from army.utils import endian
from army.core import memory
import os

class InstructionDecoder:
    """This class's only purpose is to fetch instructions and to decode them."""
    def __init__(self,instruction_set):
        self.instruction_set = instruction_set

    def decode(value):
        """Decodes instruction words into instruction and its arguments.

        value - 4-byte word containing the instruction

        Return value: 3-tuple (instruction,args,kwargs).
                      instruction - army.core.instructions.Instruction class
                      args - list of arguments to the instruction
                      kwargs - dictionary for CPU-specific return information

        """
        raise NotImplementedError

class UndefinedInstructionError(Exception):
    def __init__(self,value,desc):
        self.value = value
        self.desc = desc

    def __str__(self):
        return "Instruction %x undefined: %s" self.value,self.desc


class ARMInstructionDecoder:


class ARMCPU:

    REG_PC = 15
    REG_CPSR = 16
    REG_SP = 13
    REG_LR = 14

    def __init__(self,mem,regs,endianness=memory.EndianMemoryEngine.LITTLE_ENDIAN):
        """Initializes the state of CPU.

        mem - memory to attach to the CPU
        regs - list of register values; value = Python integer

        """
        self.mem = memory.EndianMemoryEngine(mem,endianness)
        self.regs = regs
        if self.regs = None:
            self.regs = [0 for i in range(17)]
        self.decoder = ARMInstructionDecoder()

    def get_reg(self,reg):
        value = self.regs[reg]
        if reg == REG_PC:
            value += 8
        return value

    def _executable(self,insn_bits):
        ret = False
        V,C,Z,N = endian.bits(self.regs[REG_CPSR])[28:32]
        ic = tuple(insn_bits[31:27:-1])
        if (ic == (0,0,0,0) and Z) or
           (ic == (0,0,0,1) and not Z) or
           (ic == (0,0,1,0) and C) or
           (ic == (0,0,1,1) and not C) or
           (ic == (0,1,0,0) and N) or
           (ic == (0,1,0,1) and not N) or
           (ic == (0,1,1,0) and V) or
           (ic == (0,1,1,1) and not V) or
           (ic == (1,0,0,0) and C and not Z) or
           (ic == (1,0,0,1) and not C and Z) or
           (ic == (1,0,1,0) and (N == V)) or
           (ic == (1,0,1,1) and (N != V)) or
           (ic == (1,1,0,0) and (Z == 0) and (N == V)) or
           (ic == (1,1,0,1) and (Z == 1) and (N != V)) or
           (ic == (1,1,1,0)):
            ret = True
        if ic == (1,1,1,1):
            ret = True
        return ret

    def step(self):
        """Executes one instruction."""
        # fetch
        _pc = self.regs[REG_PC]
        instruction_word = self.mem.read_multibyte(_pc,4,signed=False)
        logging.debug('Fetched instruction %x from address %x' % (
            instruction_word,_pc))
        insn_bits = endian.bits(instruction_word)
        # check if it is executable instruciton
        if self._executable(insn_bits):
            # decode
            insn,args,kwargs = self.decode(insn_bits)
            logging.debug('Executing instruction %s' % insn.str())
            # execute
            insn.execute(args)
            # increase PC to the location of next instruction
            if self.regs[REG_PC] == _pc: # PC was not modified by the instruction
                self.regs[REG_PC] += 4
        else:
            logging.debug('Instruction not executable' % instruction_word,self.regs[REG_PC])

    def run(self):
        """Runs the processor forever. Stops only on an exception (i.e. error)."""
        while True:
            self.step()

    def decode(value):
        insn_bits = value
        if isinstance(value,int):
            insn_bits = endian.bits(insn_bits)
        ib = insn_bits # shorter name since we will use this variable a lot
        tri = reversed(ib[25:28])
        kwargs = {}
        if tri[:2] == [0,0]:
            opcode = reversed(ib[21:25])
            I = ib[25] # distinguishes between the immediate and register forms of <shifter_operand>
            S = ib[20] # whether instruction updates condition codes
            if opcode[:2]==[1,0] and S == 0:
                # special cases
                raise NotImplementedError
            elif tri[2]==0 and ib[7]==1 and ib[4]==1:
                # mutiplies, extra load/stores
                raise NotImplementedError
            else:
                # data processing instructions
                Rn = self.regs[endian.value(ib[16:20])] # first source value
                Rd = endian.value(ib[12:16]) # index of destination register
                Rn2 = self._addressing_mode_1(ib[:12],immediate=I,S=S)
                insn = self.instruction_set[endian.value(reversed(opcode))]
                args = [Rd,Rn,Rn2]
                kwargs['S'] = S
        return insn,args,kwargs

    def _addressing_mode_1(self,shifter_operand,immediate,S):
        # addressing mode used for data processing instructions
        # to get value of second argument to the instruction
        old_carry = 2
        carry_out = 0
        if immediate:
            # base value is in immediate
            shift = endian.value(shifter_operand[8:]) * 2
            immed = shifter_operand[:8]
            ret = endian.value(endian.rotate(immed,-shift)) # "-" because rotating right
            if shift:
                carry_out = endian.bit(ret,31)
            else:
                carry_out = old_carry
        else:
            #FIXME carry_out needs to be produced
            # base value is in register
            value = self.get_reg(endian.value(shifter_operand[:4]))
            shift_by_imm = shifter_operand[4]
            # get the shift (or rotation) amount
            if shift_by_imm:
                # shift by immediate
                shift = endian.value(shifter_operand[7:])
                # for every shift by immediate other than logical left,
                # shift = 0 means in fact shift = 32
                if shift == 0 and not shifter_operand[4:7] == [0,0,0]:
                    shift = 32
            else:
                # shift by register value (get the least significant byte)
                shift = self.get_reg(endian.value(shifter_operand[8:])) & 0xff
            if shift == 0:
                carry_out = old_carry
            if not shifter_operand[6]:
                # logical shift
                if shifter_operand[5]:
                    # shift right
                    ret = value >> shift
                    # Python shifter shifts arithmetically, but we need 0s for
                    # new bytes unconditionally; this is problem only for
                    # shifting right; shifting left arithemtically and
                    # logically is equivalent
                    ret = endian.bits(ret)
                    ret[32-shift:32] = shift*[0]
                    ret = endian.value(ret)
                    #carry_out = endian.bit(ret
                else:
                    # shift left
                    ret = value << shift
            else:
                # arithmetic shift right or rotate right
                if shifter_operand[5]:
                    # rotate right
                    if not shift and shifter_operand[4]:
                        # rotate right with extend
                        carry_out = endian.bit(value,0)
                        value = (value >> 1) | (old_carry << 31)
                    else:
                        # rotate right
                        value = endian.rotate(endian.bits(value), shift))
                        carry_out = value[-1]
                        value = endian.value(value)
                else:
                    # arithmetic shift right
                    value = value >> shift
        if S


if __name__ == '__main__':
    import StringIO
    from army.modules.memory import PagedMemoryEngine, FileMemoryEngine
    buf = StringIO.StringIO()
    mem = PagedMemoryEngine.PagedMemoryEngine(FileMemoryEngine.FileMemoryEngine(buf))
    regs = [0 for i in range(17)]
    regs[ARMCPU.REG_PC] = 0x80000000
    cpu = ARMCPU(mem,regs)
    mem_ = memory.EndianMemoryEngine(mem,memory.EndianMemoryEngine.LITTLE_ENDIAN)
    mem_.write_multibyte(utils.value(reversed([0]*32)),4)
